package filter

import (
	"context"
	"errors"
	"github.com/afex/hystrix-go/hystrix"
	"github.com/hyperway/pkg/component"
	"github.com/hyperway/pkg/config"
	"github.com/hyperway/pkg/constant"
	"github.com/hyperway/pkg/logger"
	"sync"
)

type (
	// HystrixFilter hystrix filter
	HystrixFilter struct {
		HystrixConfig               // hystrix config
		configuredCommands sync.Map // configured commands
	}
	// HystrixConfig hystrix config
	HystrixConfig struct {
		// IsolationIdFunc 服务隔离ID生成函数
		IsolationIdFunc
		// DowngradeFunc 服务降级处理函数
		DowngradeFunc
		Timeout                int // 远程调用逻辑执行超过该时间将被强制执行超时，就进行失败回滚，单位：毫秒，默认是 1000 毫秒
		MaxConcurrentRequests  int // 表示每个 hystrix 命令最大执行的并发协程，用于进行流量控制和资源隔离，当同种的 hystrix 执行的并发数量超过了该值，请求将会直接进入到失败回滚逻辑中，并被标记为拒绝请求上报
		RequestVolumeThreshold int // 滑动窗口时间(10s)内的请求数量超过该值，断路器才会执行对应的判断逻辑，在低请求量的时候，即时这些请求全部失败，因为他只要没有超过这个值，就不会触发
		SleepWindow            int // 超时窗口时间，指的是断路器打开 SleepWindow 时长后，进入半开状态，重新允许远程调用的发生，试探下游服务是否恢复正常，如果接下来的请求都是成功的，那么断路器会关闭，否则就会重新打开
		ErrorPercentThreshold  int // 指的是，错误比例阈值，当滑动窗口时间内的错误请求频率超过这个值的时候，断路器也会打开
	}

	// IsolationIdFunc 服务隔离ID生成函数
	IsolationIdFunc func(ctx component.InnerContext) string

	// DowngradeFunc 服务降级处理函数
	DowngradeFunc func(ctx component.InnerContext, next component.FilterInvoker) *component.ServerError
)

var (
	// 默认配置
	defaultCommandConfig = &hystrix.CommandConfig{
		Timeout:                3000, // 设置超时时间，单位：毫秒
		MaxConcurrentRequests:  100,  // 默认，设置最大并发请求数
		RequestVolumeThreshold: 20,   // 默认，设置熔断器最少请求次数
		SleepWindow:            5000, // 默认，设置熔断器休眠时间
		ErrorPercentThreshold:  100,  // 默认，设置错误百分比阈值
	}
)

// FilterId filter id
func (h *HystrixFilter) FilterId() string {
	return "hystrix"
}

// DoFilter do filter
func (h *HystrixFilter) DoFilter(next component.FilterInvoker) component.FilterInvoker {

	return func(ctx component.InnerContext) *component.ServerError {

		// hystrix command执行的函数，此方法中必须要使用defer捕获panic，因为hystrix.DoC开启了一个新的协程
		runFunc := func(c context.Context) (err error) {
			defer func(traceId string) {
				if r := recover(); r != nil {
					if e, ok := r.(error); ok {
						err = e
					} else {
						err = &component.ServerError{
							StatusCode: 500,
							ErrorCode:  "constant.ErrorCodeInternal",
							Message:    "服务异常",
						}
					}
					trace := logger.TraceId(traceId)
					trace.Errorw("hystrix执行方法异常", "error", r)
				}
			}(ctx.RequestId())
			return next(ctx)
		}

		// fallback函数执行的结果
		var ret *component.ServerError
		// fallback函数，runFunc执行失败，或者熔断器打开，或者超时，都会执行fallback函数
		fallbackFunc := func(c context.Context, err error) error {

			trace := logger.Trace(c)

			defer func(traceId string) {
				if r := recover(); r != nil {
					if se, ok := r.(*component.ServerError); ok {
						ret = se
					} else if e, ok := r.(error); ok {
						ret = &component.ServerError{
							StatusCode: 500,
							ErrorCode:  "error",
							Message:    e.Error(),
						}
					} else {
						ret = &component.ServerError{
							StatusCode: 500,
							ErrorCode:  "panic",
							Message:    "服务异常",
						}
					}
					trace.Infow("hystrix服务降级异常", "error", r)
				}
			}(ctx.RequestId())

			if e, ok := err.(*component.ServerError); ok {
				ret = e
			} else if herr, ok := err.(hystrix.CircuitError); ok {
				trace.Warnw("hystrix熔断器打开", "metaKey", h.IsolationIdFunc(ctx), "error", herr)
				ret = h.DowngradeFunc(ctx, next)
			} else if errors.Is(err, context.Canceled) {
				trace.Warnw("请求取消", "metaKey", h.IsolationIdFunc(ctx), "error", herr)
				ret = &component.ServerError{
					StatusCode: 200,
					ErrorCode:  "Canceled",
					Message:    "Canceled",
					Cause:      err,
				}
			} else {
				trace.Warnw("hystrix执行方法异常", "metaKey", h.IsolationIdFunc(ctx), "error", err)
				ret = &component.ServerError{
					StatusCode: 500,
					ErrorCode:  "Internal Server Error",
					Message:    "Internal Server Error",
					Cause:      err,
				}
			}
			return nil
		}

		h.configureCommand(ctx.Service().MetaKey)
		_ = hystrix.DoC(context.WithValue(ctx.Context(), "traceId", ctx.RequestId()), h.IsolationIdFunc(ctx), runFunc, fallbackFunc)
		return ret
	}
}

func (h *HystrixFilter) configureCommand(key string) {
	if _, exists := h.configuredCommands.LoadOrStore(key, true); !exists {
		cmdConf := hystrix.CommandConfig{
			Timeout:                h.HystrixConfig.Timeout,
			RequestVolumeThreshold: h.HystrixConfig.RequestVolumeThreshold,
			MaxConcurrentRequests:  h.HystrixConfig.MaxConcurrentRequests,
			ErrorPercentThreshold:  h.HystrixConfig.ErrorPercentThreshold,
			SleepWindow:            h.HystrixConfig.SleepWindow,
		}
		hystrix.ConfigureCommand(key, cmdConf)
	}
}

// NewHystrixFilterWhitConfiguration 通过配置文件创建HystrixFilter
// 注：filter.NewHystrixFilterWhitConfiguration(config.GetWrapper("hystrix_config"))
func NewHystrixFilterWhitConfiguration(config *config.Configuration) *HystrixFilter {
	return NewHystrixFilter(HystrixConfig{
		Timeout:                config.GetInt(constant.Timeout),                // 设置超时时间，单位：毫秒
		MaxConcurrentRequests:  config.GetInt(constant.MaxConcurrentRequests),  // 默认，设置最大并发请求数
		RequestVolumeThreshold: config.GetInt(constant.RequestVolumeThreshold), // 默认，设置熔断器最少请求次数
		SleepWindow:            config.GetInt(constant.SleepWindow),            // 默认，设置熔断器休眠时间
		ErrorPercentThreshold:  config.GetInt(constant.ErrorPercentThreshold),  // 默认，设置错误百分比阈值
	})
}

// NewHystrixFilter 创建HystrixFilter
func NewHystrixFilter(config HystrixConfig) *HystrixFilter {

	if config.Timeout <= 0 {
		config.Timeout = defaultCommandConfig.Timeout
	}
	if config.MaxConcurrentRequests <= 0 {
		config.MaxConcurrentRequests = defaultCommandConfig.MaxConcurrentRequests
	}
	if config.RequestVolumeThreshold <= 0 {
		config.RequestVolumeThreshold = defaultCommandConfig.RequestVolumeThreshold
	}
	if config.SleepWindow <= 0 {
		config.SleepWindow = defaultCommandConfig.SleepWindow
	}
	if config.ErrorPercentThreshold <= 0 {
		config.ErrorPercentThreshold = defaultCommandConfig.ErrorPercentThreshold
	}

	if config.DowngradeFunc == nil {
		config.DowngradeFunc = defaultDowngrade
	}
	if config.IsolationIdFunc == nil {
		config.IsolationIdFunc = defaultIsolationId
	}
	return &HystrixFilter{config, sync.Map{}}
}

// 服务降级函数
func defaultDowngrade(ctx component.InnerContext, next component.FilterInvoker) *component.ServerError {
	return &component.ServerError{
		StatusCode: 503,
		ErrorCode:  "Service Unavailable",
		Message:    "Service Unavailable, please try again later",
	}
}

// 默认的隔离策略
func defaultIsolationId(ctx component.InnerContext) string {
	return ctx.Endpoint().MetaKey
}
